/* 
 *    author : YuBing  羽冰
 *    time   : 2020.11.18
 *description: MongoDB数据库操控工具
 *dependency module: mongoose
 */
var mongoose = require("mongoose");

var connections = {}, defaultModel;

// 数据库操作(MongoDB)
// Mongoose创建的连接自带连接池
function createLink(dbMsg) {
    // 针对findOneAndUpdate弃用警告的设置
    mongoose.set('useFindAndModify', false);
    // 默认配置
    var defaultOption = {
        useNewUrlParser: true,
        useUnifiedTopology: true,
        poolSize: 4
    },
        { mongooSetting, option, connectionType, defaultOption = defaultOption } = dbMsg, _model,
        uri = mongooSetting.uri;

    // 开始连接
    if (connectionType) {
        // 会返回一个连接对象，可以用这个连接对象去创建和检索models
        // 不停创建就会不停的返回连接对象,每个连接对象都不一样,所以可以连接到不同的库
        // 只在有需求要连接到不同的库或需要不同权限的连接时才使用
        // 使用连接创建的model是和连接绑定在一起的
        var mongoConnection = mongoose.createConnection(uri, defaultOption, function (err) {
            if (err) {
                console.log("MongoDB连接失败:" + err);
                return false;
            }
            console.log("MongoDB连接成功");
        });

        // 如果传入了创建model的设置就创建model返回
        // 如果没有传入,就返回建立的链接
        if (option) return _model = setModel(option, mongoConnection);
        else return mongoConnection;
    }
    else {
        // 单个连接，使用的是mogoose的默认连接，什么都不用干
        mongoose.connect(uri, defaultOption, function (err) {
            if (err) {
                console.log("MongoDB连接失败:" + err);
                return false;
            }
            console.log("MongoDB连接成功");
        });

        // 如果传入了创建model的设置就创建model返回
        if (option) return _model = setModel(option);
    }
}

function setModel(option, Connection) {
    // 使用指定连接生成model
    var modelFunc = mongoose;
    if (Connection) modelFunc = Connection;

    // 配置
    var { schemaSetting, modelSetting } = option,
        _model, type = type || "";

    // 生成、设置集合结构/输入规范
    var Schema = mongoose.Schema(schemaSetting);
    // 生成模型
    _model = modelFunc.model(modelSetting.modelName, Schema, modelSetting.collectionName);

    return _model;
}

function setCondition(condition) {
    // mongoDB查询专用处理condition
    // or查询检测及正则检测
    if (condition.constructor === Array) {
        // 正则检测
        condition = condition.map(function (item) {
            // 由于不知道key值,所以只能用for in直接访问内部数据
            for (var key in item) {
                // 正则就进行修改，不是正则就不变，值为空跳过本次
                if (!item[key]) continue;
                if (item[key].constructor === RegExp) {
                    item = { [key]: { $regex: item[key] } };
                }
            }
            return item;
        });

        // or查询检测
        condition = { $or: condition };
    }
    else if (condition.constructor === Object) {
        // 正则检测,如果是空对象是进不去for in的
        for (var key in condition) {
            // 正则就进行修改，不是正则就不变，值为空跳过本次
            if (!condition[key]) continue;
            if (condition[key].constructor === RegExp) {
                condition[key] = { $regex: condition[key] };
            }
        }
    }
    else condition = undefined;

    return condition;
}

// 单表查询
function select(option, connectionName) {
    var _model, { condition = {}, field, _set = {}, count = {} } = option;

    // 对条件的操作
    condition = setCondition(condition);
    if (!condition) {
        console.log("Enter right condition!!!! Must be Object/Array!!!");
        return;
    }

    // 使用指定数据库进行操作
    if (connectionName) _model = connections[connectionName].model;
    else _model = defaultModel;

    // 对文档数目计数的操作,不支持多条件计数 
    // collection.count()不久后会被废弃,因此使用以下方法作为替换
    // estimatedDocumentCount(),执行速度快,但只会计数集合中的所有数据
    // countDocuments({condition}),可以传入条件进行对指定数据的计数
    if (count.constructor !== Array) count = setCondition(count);

    return Promise.all([
        _model.find(condition, field, _set).countDocuments(count),
        _model.find(condition, field, _set)
    ])
}

// 多表查询  
// mongoDB 3.6+版本再使用这个功能,版本太低用不了这个
function joinSelect(option, connectionName) {
    /* 
     * 由于mongoDB是nosql,如果频繁需要复杂多表查询不如用sql去做
     * 或者对nosql的每条数据都添加需要的数据并采取单表查询 
     */
    // 基础设置
    var { join, showField, condition, set } = option;
    var finalFind = [];
    if (set) var { sort, limit, skip } = set;

    //  查询设置
    var LinkTable = [];      /* 最终输入设置 */
    var addFields = {};      /* 依赖于showField的值设置 */
    var project = {};        /* 依赖于showField的值设置 */
    var lookup,              /* 关联的表的设置 */
        unwind;              /* 依赖于lookup的as字段设置 */

    // 关联的表的设置和分段设置
    if (join.constructor === Object) {
        lookup = { $lookup: join };
        LinkTable.push(lookup);

        unwind = { $unwind: "$" + join.as };
        LinkTable.push(unwind);
    }
    else if (join.constructor === Array) {
        // 封装的时候
        // 暂时不知道间隔lookup、unwind、lookup、unwind的插入
        // 和统一lookup一起插入、unwind一起插入
        // 这两者之间有什么影响
        // 如果后面知道了,影响大的话就改一下
        // 思路：处理后的unwind就先存储到一个数组里之后再forEach逐条插入LinkTable中
        join.forEach(function (item) {
            if (item.constructor !== Object) return;

            lookup = new Object();
            lookup.$lookup = item;
            LinkTable.push(lookup);

            // 顺序混乱的插入设置,试试看
            unwind = new Object();
            unwind.$unwind = "$" + item.as;
            LinkTable.push(unwind);
        });
    }
    else {
        console.log("join's type Error!!!!");
        return;
    };

    // 最终输出结果的结构设置
    if (showField) {
        if (showField.constructor !== Object) {
            console.log("ShowField's type Error!!!!");
            return;
        }

        for (var prop in showField) {
            var item = showField[prop];

            // 传入数字1,直接给project设置
            if (Number(item)) project[prop] = Number(item);
            else if (item.constructor === Object) {
                // 传入对象属性设置,处理后再设置给project
                addFields[prop] = "$" + item.as + "." + item.TableField;

                project[prop] = 1;
            }
        };
        LinkTable.push({ $addFields: addFields });
    }

    // 条件查询的配置
    // 放在最后等连表完成后再进行条件查询
    if (condition) {
        condition = setCondition(condition);
        if (!condition) {
            console.log("Enter right condition!!!! Must be Object/Array!!!");
            return;
        }

        LinkTable.push({ $match: condition });
    }
    if (sort) LinkTable.push({ $sort: sort });
    if (limit) LinkTable.push({ $limit: limit });
    if (skip) LinkTable.push({ $skip: skip });
    // 特定字段的显示与否放在最后进行控制
    LinkTable.push({ $project: project });

    // 使用指定数据库进行操作
    if (connectionName) _model = connections[connectionName].model;
    else _model = defaultModel;

    // 最后的查询配置
    finalFind.push(_model.aggregate(LinkTable).count("count"));
    finalFind.push(_model.aggregate(LinkTable));

    // 开始查询
    return Promise.all(finalFind).then(function(result){
        return result = [result[0][0]["count"],result[1]];
    })
}

function insert(data, connectionName) {
    var _model, mongoFunc;

    // 使用指定数据库进行操作
    if (connectionName) _model = connections[connectionName].model;
    else _model = defaultModel;

    // 根据不同方式进行不同操作
    if (data.constructor === Object) mongoFunc = () => _model(data).save();
    else if (data.constructor === Array) mongoFunc = () => _model.insertMany(data);
    else {
        console.log("Enter right format of data");
        return false;
    }

    return mongoFunc()
}

function move(option, connectionName) {
    var _model, mongoFunc, promise, finalResult = [],
        { condition = {}, type = "many" } = option;

    // 使用指定数据库进行操作
    if (connectionName) _model = connections[connectionName].model;
    else _model = defaultModel;

    // 根据类型不同进行不同操作
    if (type === "one") mongoFunc = () => _model.deleteOne(condition);
    else if (type === "all") {
        // 清空条件,阻止进入检测
        condition = {};
        mongoFunc = () => _model.remove()
    }
    else mongoFunc = () => _model.deleteMany(condition);

    if (condition.constructor === Array) {
        // 多次删除操作
        var promiseFunc = mongoFunc;
        promise = new Promise(function (resolve, reject) {
            condition.forEach(function (item) {
                // 对每个条件进行多次删除
                promiseFunc().then(function (result) {
                    finalResult.push(result);
                }).catch(function (err) {
                    console.log("Fail to delete: " + err);
                    reject(err);
                });
            });
            resolve(finalResult);
        });

        mongoFunc = () => promise;
    }

    return mongoFunc()
}

function update(option, connectionName) {
    var _model, mongoFunc,
        { update, condition = {}, set = {}, type = "many", kind = "set" } = option;

    if (kind !== "set" && kind !== "inc") {
        console.log("kind Type Error!!!");
        return;
    }
    kind = "$" + kind;
    update = { [kind]: update };

    if (!update) {
        console.log("you must update something");
        return;
    }

    // 使用指定数据库进行操作
    if (connectionName) _model = connections[connectionName].model;
    else _model = defaultModel;

    // 根据不同type进行不同操作
    if (type === "one") mongoFunc = () => _model.updateOne(condition, update, set);
    else if (type === "return") {
        mongoFunc = () => _model.findOneAndUpdate(condition, update, set);
    }
    else mongoFunc = () => _model.updateMany(condition, update, set);

    return mongoFunc()
}

function useModel(_model) {
    // 挂载model
    if (_model.constructor === Array) {
        _model.forEach(function (item) {
            var _connection = { model: item.model };
            connections[item.connectionName] = _connection;
        })
    }
    else defaultModel = _model;
}

module.exports = {
    createLink,
    setModel,
    useModel,
    select,
    joinSelect,
    insert,
    delete: move,
    update
};